js map 是有序的?

Map 对象是个很特别的存在,它是一种键值对的集合。在 Map 中,每个键都是独一无二的,不能重复。这种设计确保了每个键值对在 Map 中都有唯一的位置。

当我们使用 for...of 循环遍历 Map 时,每次迭代都会返回一个形式为 [key, value] 的数组,并且这些键值对的顺序是按照它们被插入时的顺序进行的。这就意味着,当你用 set() 方法添加键值对时,Map 会记住它们插入的顺序,之后的迭代也是基于这个顺序的。

按照规范,Map 的实现要求“平均访问时间与集合中的元素数量呈次线性关系”。这听起来有点技术,但简单来说,就是 Map 的操作效率要比直接操作数组或者列表更高。因此,Map 可以内部实现为哈希表(查找操作是 O(1) 复杂度)、搜索树(查找操作是 O(log(N) 复杂度)等,只要能保证复杂度低于 O(N) 就可以。

至于键的相等性,Map 是基于“零值相等”算法来比较的。这与 JavaScript 中的 === 运算符有点不同。特别是,Map 将 NaN 视为等同的,这一点与 NaN !== NaN 有所区别,但这是为了确保 Map 在处理特殊值时的一致性。

Object 和 Map 在很多情况下都可以用来存储键值对,但它们有一些显著的区别。以下是它们之间的详细对比,帮助你在适当的场景中选择合适的数据结构:

键管理

意外的键

  • Map:默认不包含任何键,只包含显式添加的键值对。这意味着你不必担心意外的键。
  • Object:有原型,因此它包含一些默认的键(如 toStringhasOwnProperty 等)。如果不小心,这些默认键可能会与自己的键冲突。尽管可以使用 Object.create(null) 来避免这种情况,但这种做法并不常见。

安全性

  • Map:可以安全地使用用户提供的键值,不会因为覆盖原型而引发安全问题。
  • Object:设置用户提供的键值对可能允许攻击者覆盖对象的原型,导致对象注入攻击。尽管可以使用 null 原型对象来缓解这个问题,但并不普遍。

键的类型

  • Map:键可以是任何值(包括函数、对象或任何原始值)。
  • Object:键必须是字符串或 Symbol

键的顺序

  • Map:键以插入顺序进行排序。迭代条目时按照插入的顺序进行。
  • Object:虽然现代 JavaScript 规范规定对象键是有序的,但排序规则较为复杂且在历史上并非总是如此。因此,最好不要依赖对象属性的顺序。

大小

  • Map:项目数量可以通过 size 属性直接获得。
  • Object:确定对象中项目的数量需要通过 Object.keys() 方法获得数组的长度,这个过程更为麻烦和低效。

迭代

  • Map:是可迭代对象,可以直接用 for...of 进行迭代。
  • Object:没有实现迭代协议,不能直接用 for...of 迭代。不过,可以使用 Object.keys()Object.entries()for...in 来迭代对象的属性。

性能

  • Map:在频繁添加和删除键值对的场景中表现更好,专门为此进行了优化。
  • Object:未针对频繁添加和删除键值对进行优化。

序列化和解析

  • Map:没有原生的序列化或解析支持,但可以使用 JSON.stringify() 及其 replacer 参数和 JSON.parse() 及其 reviver 参数来构建自己的序列化和解析支持。
  • Object:原生支持使用 JSON.stringify() 序列化对象为 JSON,并使用 JSON.parse() 解析 JSON 为对象。

选择建议

  • 使用 Map 的情况:

  • 当键的类型多样(不只是字符串或 Symbol)。

  • 当需要频繁添加和删除键值对。

  • 当需要按照插入顺序迭代键值对。

  • 当需要一个更加安全的数据结构,不用担心意外的原型污染。

  • 使用 Object 的情况:

  • 当键都是字符串或 Symbol

  • 当需要使用 JSON 序列化和解析。

  • 当需要一个简单的结构,不需要频繁的增删操作。

通过理解这些差异,你可以更好地选择合适的数据结构来满足具体需求。